package com.tencent.cloud.iov.processor.block;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.tencent.cloud.iov.block.Block;
import com.tencent.cloud.iov.processor.utils.LogHelper;

import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;

/**
 * 区块布局工厂类创建处理器
 *
 * @author showxiao
 * @date 2018/4/16
 */

@AutoService(Processor.class)
public class BlockProcessor extends AbstractProcessor {

    private static final String CLASS_NAME_POSTFIX = "$LayoutFactory";
    private static final String METHOD_NAME = "create";

    private LogHelper mLogHelper;

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new HashSet<>(1);
        set.add(Block.class.getCanonicalName());
        return set;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mLogHelper = new LogHelper(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        try {
            Map<TypeMirror, TypeElement> itemLayoutMap = new LinkedHashMap<>();

            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Block.class);
            for (Element element : elements) {
                if (element.getKind() != ElementKind.CLASS) {
                    mLogHelper.error(element, "Annotation @Block should be used for classes only");
                    return true;
                }

                TypeElement typeElement = (TypeElement) element;
                List<? extends TypeMirror> typeArguments =
                        ((DeclaredType) (typeElement).getSuperclass()).getTypeArguments();

                if (typeArguments == null || typeArguments.isEmpty()) {
                    mLogHelper.error(element, "BlockLayout with @Block should has IBlockItem " +
                            "as first generic type in super class\n" +
                            "i.e.: class TextBlockLayout extends BaseBlockLayout<ITextBlockItem>");
                    return true;
                }

                try {
                    generatorLayoutFactory(typeElement, typeArguments.get(0));
                } catch (Exception e) {
                    mLogHelper.error(element, e.toString());
                }
            }
        } catch (Exception e) {
            mLogHelper.error(null, e.toString());
        }

        return true;
    }

    private void generatorLayoutFactory(TypeElement typeElement, TypeMirror typeMirror) throws IOException {
        Element element = processingEnv.getTypeUtils().asElement(typeMirror);

        MethodSpec.Builder builder = MethodSpec.methodBuilder(METHOD_NAME)
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addParameter(Class.class, "itemClass")
                .returns(TypeName.get(typeElement.asType()))
                .addStatement("return new $T()", typeElement);

        String itemClassName = element.getSimpleName()
                .toString();
        TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(itemClassName + CLASS_NAME_POSTFIX)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(builder.build())
                .addJavadoc(getJavaDoc());

        writeToFile(typeBuilder, element);
    }

    private String getJavaDoc() {
        return "注解布局工厂类：通过注解方式定义区块数据对应区块布局的创建\n\n" +
                "此类通过编译时注解自动生成\n\n" +
                "对于需要定义区块布局创建的类，\n" +
                "只需要声明区块布局定义的区块数据泛型信息 {@link IBlockLayout<T>}，并在区块布局类上添加 @Block 注解，\n" +
                "重新编译后，便会生成此区块布局工厂类，将区块数据与区块布局的创建关联起来\n\n" +
                "i.e.:\n\n" +
                "@Block\n" +
                "public class TextBlockLayout implements IBlockLayout<ITextBlockItem> {}\n";
    }

    /**
     * 生成 .java 文件
     *
     * @param typeBuilder
     * @param element
     * @throws IOException
     */
    private void writeToFile(TypeSpec.Builder typeBuilder, Element element) throws IOException {
        final String packageName = processingEnv.getElementUtils()
                .getPackageOf(element).getQualifiedName().toString();
        JavaFile.builder(packageName, typeBuilder.build())
                .build()
                .writeTo(processingEnv.getFiler());
    }
}
